package com.example.demo.utils;


import com.example.demo.identity.GroupMsg;
import com.example.demo.repository.ChatMsgRepository;
import com.example.demo.repository.GroupMsgRepository;
import com.example.demo.services.RedisService;
import com.example.demo.services.MinioService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.minio.errors.*;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Getter
@Setter
@Component
public class FileHandler {          // springboot中的一些类可能为工具类或不适合添加注解，在注入service的时候可以使用构造函数

    //    存储文件元数据信息
    private final static ConcurrentHashMap<String, FileMetadata> FileMetadataMap = new ConcurrentHashMap<>();

    private final static Logger logger = LoggerFactory.getLogger(FileHandler.class);

    // 定义分片大小
    private static final int CHUNK_SIZE = 3 * 1024 * 1024;

    // 定义消息头的固定长度
    private static final int HEADER_SIZE = 24;

    private MinioService minioService;
    private RedisService redisService;
    private GroupMsgRepository groupMsgRepository;
    private ChatMsgRepository chatMsgRepository;


    public FileHandler(MinioService minioService, RedisService redisService, GroupMsgRepository groupMsgRepository,
                       ChatMsgRepository chatMsgRepository) {
        this.minioService = minioService;
        this.redisService = redisService;
        this.groupMsgRepository = groupMsgRepository;
        this.chatMsgRepository = chatMsgRepository;
    }

    //    定义支持的文件类型以及MIME类型
    private static final Map<String, String> SUPPORTED_FILE_TYPES = new HashMap<>();

    // 处理二进制消息（文件分片或完整文件）
    public void handleBinaryMessage(WebSocketSession session, BinaryMessage binaryMessage) {
        try {
//            获取二进制数组
            byte[] payload = binaryMessage.getPayload().array();
            // 检查消息长度是否足够
            if (payload.length < HEADER_SIZE) {
                throw new IllegalArgumentException("Binary message is too short to extract 24 bytes");
            }
//            提取字节数组中的前24为字节数据（包含了文件类型和文件唯一数据）
            byte[] first24Bytes = Arrays.copyOfRange(payload, 0, 24);
//            将字节转换为字符串
            String extractedData = new String(first24Bytes, StandardCharsets.UTF_8);
            String fileType = new String(Arrays.copyOfRange(first24Bytes, 0, 4), StandardCharsets.UTF_8);
            String fileKey = new String(Arrays.copyOfRange(first24Bytes, 4, 24), StandardCharsets.UTF_8);
            if (fileType.equals("SMLF")) {
                handleSmallFile(session, binaryMessage, fileKey);
            } else if (fileType.equals("FCHK")) {
                handleChunkFile(session, binaryMessage, fileKey);
            } else {
                throw new IOException("未知的数据传输模式");
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    //    处理文件元数据信息
    public void handleFileMedata(Map<String, Object> messageMap, WebSocketSession targetSession, String senderId, String receiverId, String mimeType,
                                 String userNickName, String userNickAvatar) {
        try {
//            提取文件元数据并存储
            String fileName = (String) messageMap.get("fileName");
            String fileKey = (String) messageMap.get("fileKey");
            Object fileSizeObj = messageMap.get("fileSize");
//            构建元数据类型
            String fileMetaDataType = "snow/file/info";
            long fileSize;
            if (fileSizeObj instanceof Long) {
                fileSize = (Long) fileSizeObj;
            } else if (fileSizeObj instanceof Integer) {
                fileSize = ((Integer) fileSizeObj).longValue();
            } else {
                throw new IllegalArgumentException("Invalid type for fileSize: " + fileSizeObj.getClass().getName());
            }

            String fileUrl = minioService.getTemporaryUrl(fileName);

//            FileMetadata fileMetadata = new FileMetadata(targetSession, senderId, receiverId, fileName, fileKey, fileSize,"snow/file/info);
            FileMetadata fileMetadata = FileMetadata.builder()
                    .targetSession(targetSession)
                    .senderId(senderId)
                    .receiverId(receiverId)
                    .fileName(fileName)
                    .fileKey(fileKey)
                    .fileURL(fileUrl)
                    .fileSize(fileSize)
                    .mimeType(mimeType)
                    .userNickName(userNickName)
                    .userNickAvatar(userNickAvatar)
                    .messageType(fileMetaDataType).build();
            FileMetadataMap.put(fileKey, fileMetadata);


            stockFileInfoInMongoDB(messageMap, senderId, receiverId, fileMetadata.getFileURL());
        } catch (Exception e) {
            System.out.println("在这里出错了");
            logger.error(String.valueOf(e));
        }
    }

    //    处理小文件的函数
    private void handleSmallFile(WebSocketSession session, BinaryMessage binaryMessage, String fileKey) {
        try {
//            获取文件元数据
            FileMetadata fileMetadata = FileMetadataMap.get(fileKey);
            if (fileMetadata == null) {
                throw new IOException("File metadata not found for fileKey: " + fileKey);
            }
//            获取消息头和文件内容     在字节数组24处开始，长四个字节
            byte[] payload = binaryMessage.getPayload().array();
            byte[] fileContent = Arrays.copyOfRange(payload, HEADER_SIZE, payload.length);
//            保存文件      将文件上传到了minio中，本地存储一份
            Path filePath = Paths.get("data/files", fileMetadata.getFileName());
            Files.createDirectories(filePath.getParent());
            Files.write(filePath, fileContent);

//            将聊天文件上传到minio中，保证持久化存储
            minioService.putFileInMinio("chat-file", fileContent, fileMetadata);

            //            获取文件访问链接
            String fileUrl = minioService.getObjectVisitURL("chat-file", fileMetadata.getFileName(), fileKey);

//            设置访问url
            fileMetadata.setFileURL(fileUrl);

//            存储文件链接到 CompletableFuture
//            CompletableFuture<String> future = new CompletableFuture<>();
//            future.complete(fileUrl);
//            FileUrlStore.storeFileUrlFuture(fileKey, future);

            //                发送消息到RabbitMQ
//            sendFileInfoMessgaeToRabbitMQ(new FileUploadCompleteEvent(fileKey, fileMetadata.getFileName(), fileUrl, true));
//            将文件消息存储到redis
//            storeFileInfoInRedis(fileMetadata);
//            发送文件信息
//            sendFileInformation(fileMetadata.getTargetSession(), fileMetadata.getSenderId(),
//                    fileMetadata.getFileName(), fileMetadata.getFileSize(), fileContent,
//                    fileMetadata.getFileKey());
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    //    处理端点传输文件的函数       文件的传输是进行分片处理的，而不是一次性传输整个文件。每次传输的分片都会被写入临时文件，直到接收完毕后才进行重组文件并执行后续的操作
    private void handleChunkFile(WebSocketSession session, BinaryMessage binaryMessage, String fileKey) {
        try {
            // 获取文件元数据
            FileMetadata fileMetadata = FileMetadataMap.get(fileKey);
            if (fileMetadata == null) {
                throw new IOException("File metadata not found for fileKey: " + fileKey);
            }

            // 提取消息头和文件分片内容
            byte[] payload = binaryMessage.getPayload().array();
            byte[] chunkContent = Arrays.copyOfRange(payload, HEADER_SIZE, payload.length);

            // 提取分片总数（从字节24开始，4字节）
//            byte[] totalChunksBytes = Arrays.copyOfRange(payload, 24, 28);
//            int totalChunks = ByteBuffer.wrap(totalChunksBytes).getInt();
            int totalChunks = ByteBuffer.wrap(Arrays.copyOfRange(payload, 24, 28)).getInt();
            // 提取当前分片索引（从字节28开始，4字节）
//            byte[] chunkIndexBytes = Arrays.copyOfRange(payload, 28, 32);
//            int chunkIndex = ByteBuffer.wrap(chunkIndexBytes).getInt();
            int chunkIndex = ByteBuffer.wrap(Arrays.copyOfRange(payload, 28, 32)).getInt();

            // 创建临时文件存储分片
            Path tempFilePath = Paths.get("data/files", fileKey + "_temp");
            Files.createDirectories(tempFilePath.getParent());

            // 将分片写入临时文件
            try (RandomAccessFile tempFile = new RandomAccessFile(tempFilePath.toFile(), "rw")) {
                tempFile.seek(chunkIndex * CHUNK_SIZE);
                tempFile.write(chunkContent);
            }

            // 检查是否所有分片都已接收
            if (chunkIndex == totalChunks - 1) {
                // 重组文件
                Path finalFilePath = Paths.get("data/files", fileMetadata.getFileName());
                Files.createDirectories(finalFilePath.getParent());

                // 创建最终文件      将文件上传到了minio中所以不进行本地写入文件的操作
                try (FileOutputStream fos = new FileOutputStream(finalFilePath.toFile())) {
                    // 从临时文件中读取所有分片并写入最终文件
                    try (RandomAccessFile tempFile = new RandomAccessFile(tempFilePath.toFile(), "r")) {
                        byte[] buffer = new byte[CHUNK_SIZE];
                        for (int i = 0; i < totalChunks; i++) {
                            tempFile.seek(i * CHUNK_SIZE);
                            int bytesRead = tempFile.read(buffer);
                            fos.write(buffer, 0, bytesRead);
                        }
                    }
                }

                // 删除临时文件
                Files.delete(tempFilePath);
//                读取合并之后的文件内容并发送
                byte[] finalChunkContent = Files.readAllBytes(finalFilePath);
//                发送文件
//                sendFileInformation(fileMetadata.getTargetSession(), fileMetadata.getSenderId(),
//                        fileMetadata.getFileName(), fileMetadata.getFileSize(), finalChunkContent,
//                        fileMetadata.getFileKey());

//                System.out.println("断点传输文件重组成功，文件名：" + fileMetadata.getFileName());
                minioService.putFileInMinio("chat-file", finalChunkContent, fileMetadata);
                //            获取文件访问链接
                String fileUrl = minioService.getObjectVisitURL("chat-file", fileMetadata.getFileName(), fileKey);
//                将文件访问链接存储到元数据对象中
                fileMetadata.setFileURL(fileUrl);

//                发送消息到RabbitMQ
//                rabbitTemplate.convertAndSend(RabbitMQConfig.FILE_UPLOAD_EXCHANGE, RabbitMQConfig.ROUTING_KEY,
//                        new FileUploadCompleteEvent(fileKey, fileMetadata.getFileName(), fileUrl, true));


                //                将文件消息存储到redis中
//                storeFileInfoInRedis(fileMetadata);
            }
        } catch (Exception e) {
            logger.error("Error handling chunk file: {}", e.getMessage(), e);
        }
    }


    //    向用户发送文件
    public void sendFileInformation(WebSocketSession session, String senderId, String fileName, long fileSize, byte[] fileContent, String fileKey) {
        try {
            // 定义消息头的大小（假设消息头固定为256字节）
            final int HEADER_SIZE = 256;

            // 创建并清零消息
            byte[] header = new byte[HEADER_SIZE];
            Arrays.fill(header, (byte) 0);

            // 填充 senderId（固定长度为15字节）
            byte[] senderIdBytes = senderId.getBytes(StandardCharsets.UTF_8);
            System.arraycopy(senderIdBytes, 0, header, 0, Math.min(senderIdBytes.length, 15));

            // 填充文件名（文件名不会超过50字节，否则截断）
            byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);
            int fileNameLength = Math.min(fileNameBytes.length, 50);
            System.arraycopy(fileNameBytes, 0, header, 15, fileNameLength); // 文件名从第15字节开始
            // 如果文件名字节长度不足 50，用空字符填充
            for (int i = fileNameLength; i < 50; i++) {
                header[15 + i] = '*';     // 使用空字符填充
            }

            // 填充文件大小（使用64位整数）
            ByteBuffer.wrap(header, 65, 8).putLong(fileSize); // 文件大小从第65字节开始

            // 填充单位（固定长度为4字节，不足部分填充空格）
            String unit = fileSize < 1024 * 1024 ? "KB" : "MB";
            byte[] unitBytes = unit.getBytes(StandardCharsets.UTF_8);
            System.arraycopy(unitBytes, 0, header, 73, unitBytes.length); // 单位从第73字节开始
            if (unitBytes.length < 4) {
                Arrays.fill(header, 73 + unitBytes.length, 77, (byte) ' '); // 填充空格
            }

//            填充文件唯一标识符（固定长度，20字节）
            byte[] fileKeyBytes = fileKey.getBytes(StandardCharsets.UTF_8);
            System.arraycopy(fileKeyBytes, 0, header, 77, Math.min(fileKeyBytes.length, 20));       // 在弟77字节开始，长度固定为20字节


            // 构建完整的二进制消息（消息头 + 文件内容）
            byte[] message = new byte[HEADER_SIZE + fileContent.length];
            System.arraycopy(header, 0, message, 0, HEADER_SIZE);
            System.arraycopy(fileContent, 0, message, HEADER_SIZE, fileContent.length);

            // 发送消息
            if (session != null && session.isOpen()) {
                session.sendMessage(new BinaryMessage(message));
            } else {
//                throw new IOException("Session not open");
                return;
            }
        } catch (Exception e) {
            logger.error("Error sending file information", e);
        }
    }


    //    将文件消息存储到redis
    private void storeFileInfoInRedis(FileMetadata fileMetadata) throws JsonProcessingException {
//        获取基本信息
        String senderId = fileMetadata.getSenderId();
        String receiverId = fileMetadata.getReceiverId();
        // 生成消息的唯一id
        String messageId = UUID.randomUUID().toString().substring(0, 8);
//        构建消息唯一Id
        String chatId = senderId + "-" + receiverId;
//        将文件元数据信息转换为一个字符串
        String message = fileMetadata.toJson();
//        chatService.saveChatMessage(senderId, receiverId, message);
    }


    //    将文件元数据信息落库到MongoDB
    private void stockFileInfoInMongoDB(Map<String, Object> messageMap, String senderId, String receiverId, String fileUrl) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        ObjectMapper objectMapper = new ObjectMapper();
        String messageType = messageMap.get("fileReceiverType").toString();
        String userNickName = messageMap.get("userNickName").toString();
        String userNickAvatar = messageMap.get("userNickAvatar").toString();
//        获取文件元数据基本信息（文件名、文件唯一ID）
        String fileName = messageMap.get("fileName").toString();
        String fileKey = messageMap.get("fileKey").toString();
        String fileSize = messageMap.get("fileSize").toString();
        String mimeType = FileTypeInference.inferMimeType(fileName);

//        获取文件访问链接
//        minioService.getFileUrlInfoMinioByFileKey(fileKey, fileName);

        HashMap<String, String> fileInfo = new HashMap<>();
        fileInfo.put("fileName", fileName);
        fileInfo.put("fileKey", fileKey);
        fileInfo.put("fileSize", fileSize);
        fileInfo.put("fileUrl", fileUrl);
        fileInfo.put("userNickName", userNickName);
        fileInfo.put("mimeType", mimeType);
        fileInfo.put("userNickAvatar", userNickAvatar);
//                将文件信息转换为一个字符串
        String fileInfoString = objectMapper.writeValueAsString(fileInfo);
        if (messageType.equals("group-chat-file-message")) {
            //                构建实体
            GroupMsg entity = GroupMsg.builder()
                    .groupId(messageMap.get("toChatId").toString()).messageKey(UUID.randomUUID().toString().substring(0, 10)).userNickName(userNickName)
                    .userNickAvatar(userNickAvatar).senderId(messageMap.get("fromUserId").toString()).message(fileInfoString).type(MsgType.FILE)
                    .timeStamp(Instant.now()).at(null).build();
            groupMsgRepository.save(entity);
        } else {
            redisService.saveChatMessage(senderId, receiverId, fileInfoString, userNickName, userNickAvatar);
        }

    }

//    将文件元数据信息存储到MongoDB中


}